/*
 * Copyright (C) 2013 Manoel Afonso Filho
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package agimagem.concrete;

import agimagem.fitness.Fitness;
import agimagem.populacao.Individuo;
import agimagem.strategy.Crossover;
import agimagem.util.AGChave;
import agimagem.util.Util;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * Implementação do Crossover de Um Ponto (Single point crossover).
 * Um ponto de crossover é escolhido aleatoriamente. Os dados binários de cada
 * um dos pais até o ponto escolhido é copiado para os respectivos filhos.
 * Em seguida, os dados binários a partir do ponto de crossover são copiados do
 * outro pai.
 * <p>
 * Após o processo de crossover em si, é escolhido o parâmetro <code>z</code>
 * aleatoriamente no intervalo [0, 7]. A partir de <code>z</code> é calculado o
 * parâmetro <code>x</code>. Feito isso, é realizada a operação XOR entre cada
 * byte dos filhos gerados e o parâmetro <code>x</code>.
 * </p>
 * <p>
 * Este operador utiliza dois indivíduos de entrada e retorna dois novos
 * indivíduos.
 * </p>
 * @author Manoel Afonso Filho
 */
public class ImgCrossover implements Crossover {

    private static Logger log = LogManager.getLogger(ImgCrossover.class.getName());

    @Override
    public Individuo[] executar(Individuo... ind) {
        byte[] img1 = ind[0].getCiphertext();
        byte[] img2 = ind[1].getCiphertext();
        int tamanhoFilhos = 0;

        if(img1.length == img2.length){
            tamanhoFilhos = img1.length;
        } else{
            log.error("Indivíduos de tamanhos diferentes. i1 = {}, i2 = {}.", img1.length, img2.length);
            System.exit(-1);
        }

//        int pontoCrossover = Util.randInt(1, tamanhoFilhos - 1);
        int pontoCrossover = (int) (Math.random() * tamanhoFilhos);//TODO melhorar isso

        byte[] n1 = new byte[tamanhoFilhos];
        byte[] n2 = new byte[tamanhoFilhos];

        byte[] parteOriginal1 = new byte[tamanhoFilhos - pontoCrossover];
        byte[] parteOriginal2 = new byte[tamanhoFilhos - pontoCrossover];

        // Crossover de um ponto.
        for (int i = 0; i < pontoCrossover; i++) {
            n1[i] = img1[i];
            n2[i] = img2[i];
        }
        for (int i = pontoCrossover; i < tamanhoFilhos; i++) {
            n1[i] = img2[i];
            n2[i] = img1[i];
        }
        // Guarda as partes originais
        for (int i = 0; i < parteOriginal1.length; i++) {
            parteOriginal1[i] = img1[pontoCrossover + i];
            parteOriginal2[i] = img2[pontoCrossover + i];
        }

        // Criptografia
        byte z1 = (byte)(Util.sr.nextDouble() * 8);
        byte z2 = (byte)(Util.sr.nextDouble() * 8);

        byte x1 = calcularX(z1);
        byte x2 = calcularX(z2);

        for (int i = 0; i < tamanhoFilhos; i++) {
            byte tmp1 = n1[i];
            byte tmp2 = n2[i];
            n1[i] = (byte) (tmp1 ^ x1);
            n2[i] = (byte) (tmp2 ^ x2);
        }

        // Novos indivíduos
        Individuo ind1 = new Individuo(n1);
        Individuo ind2 = new Individuo(n2);

        ind1.setChaves(ind[0].getChaves());
        ind2.setChaves(ind[1].getChaves());

        ind1.getChaveDaGeracaoAtual().setCrossover(z1, pontoCrossover, parteOriginal1);
        ind2.getChaveDaGeracaoAtual().setCrossover(z2, pontoCrossover, parteOriginal2);

        ind1.setFitness(Fitness.calcular(ind1));
        ind2.setFitness(Fitness.calcular(ind2));

        Individuo[] novos = new Individuo[]{ind1, ind2};

        return novos;
    }

    /**
     * Cálculo do parâmetro <code>x</code>.<br/>
     * <code>x = z XOR ( z &laquo; 4 ).</code>
     * @param z Parâmetro <code>z</code> &isin; [0, 7].
     * @return Parâmetro <code>x</code>.
     */
    private byte calcularX(byte z){
        return (byte) (z ^ (z << 4));
    }

    @Override
    public byte[] reverso(byte[] ciphertext, AGChave chave) {
        int pc = chave.getPontoCrossover();
        if (pc != Integer.MIN_VALUE) {

            byte x = calcularX(chave.getZ());
            byte[] pori = chave.getParteOriginal();

            for (int i = 0; i < ciphertext.length; i++) {
                ciphertext[i] = (byte) (ciphertext[i] ^ x);
            }

            for (int i = pc; i < ciphertext.length; i++) {
                ciphertext[i] = pori[i - pc];
            }

        }

        return ciphertext;
    }

}
